package ch.nostromo.tiffanys.engines.tiffanys;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import uk.ac.cam.cl.projectcharlie2010.Charlies;
import uk.ac.cam.cl.projectcharlie2010.engines.Engine;
import ch.nostromo.tiffanys.engines.EngineResult;
import ch.nostromo.tiffanys.engines.EngineSettings;
import ch.nostromo.tiffanys.engines.tiffanys.ai.OpeningBook;
import ch.nostromo.tiffanys.engines.tiffanys.ai.TiffanysAiTools;
import ch.nostromo.tiffanys.engines.tiffanys.ai.TranspositionTableTiff;
import ch.nostromo.tiffanys.engines.tiffanys.board.TiffanysBoard;
import ch.nostromo.tiffanys.engines.tiffanys.board.TiffanysMove;
import ch.nostromo.tiffanys.engines.tiffanys.board.TiffanysMoveTools;
import ch.nostromo.tiffanys.engines.tiffanys.board.pieces.TiffanysPiece;
import ch.nostromo.tiffanys.game.board.Board;
import ch.nostromo.tiffanys.game.game.Game;
import ch.nostromo.tiffanys.game.move.MoveInput;

public class TiffanysEngine extends Engine {
	static Logger logger = Logger.getLogger(TiffanysEngine.class.getName());

	private TiffanysMove[] currentMoveList;

	private TiffanysBoard tboard;

	private int currentTargetDepth = 0;

	private static Random random = null;

	public static boolean abortable = false;

	private long statSearchStarted = 0;

	public int statNodes = 0;

	public int statPositionsEvaluated = 0;

	public int statAbCutOffs = 0;

	public static StringBuffer statEngineInfo = new StringBuffer();

	public TiffanysEngine() {
		TiffanysPiece.initialize();
		TiffanysAiTools.initialize();
	}

	private int convertColor(int colorToMove) {
		if (colorToMove == Game.COLOR_WHITE) {
			return Board.OWNER_WHITE;
		} else {
			return Board.OWNER_BLACK;
		}
	}

	private EngineResult playOpening(int colorToMove, String playedPGN) {
		try {
			// Check for opening Book moves
			String opening = OpeningBook.getInstance().checkForMoves(playedPGN);
			if (opening != null) {

				TiffanysMove[] tiffMoves = currentMoveList.clone();

				MoveInput[] moves = new MoveInput[tiffMoves.length];
				for (int i = 0; i < moves.length; i++) {
					moves[i] = tiffMoves[i].convertToMoveInput();
				}

				logger.finer("Selected opening: " + opening);

				int pref = 0;
				int suf = 0;
				if (colorToMove == Game.COLOR_BLACK) {
					pref = playedPGN.length() + 1;
					suf = opening.indexOf(" ", pref);
					if (suf == -1) {
						suf = opening.length();
					}

				} else {
					pref = opening.indexOf(".", playedPGN.length()) + 1;
					suf = opening.indexOf(" ", pref);
				}

				String nextMove = opening.substring(pref, suf);

				TiffanysMove move = TiffanysMoveTools.alterCheckSanMove(nextMove, currentMoveList);
				MoveInput mi = move.convertToMoveInput();

				logger.finer("Selected move: " + nextMove);

				double ms = System.currentTimeMillis() - statSearchStarted;

				EngineResult er = new EngineResult(mi, moves, ms, "", opening);

				return er;

			}
		} catch (Exception e) {
			logger.severe(e.getMessage());
		}

		return null;

	}

	@SuppressWarnings("unchecked")
	@Override
	public EngineResult engineSelectNextMove(Board pBoard, int colorToMove, String playedPGN) {
		initAi();

		// Convert Board
		tboard = new TiffanysBoard(pBoard);
		long[] mainBoard = tboard.getBoard();

		// Convert color
		int color = convertColor(colorToMove);

		// First level ... check for check & sort by best move !!
		currentMoveList = TiffanysBoard.generateLegalMovesList(mainBoard, color);

		EngineResult openingResult = playOpening(colorToMove, playedPGN);

		if (openingResult != null) {
			return openingResult;
		}





		currentTargetDepth = 0;
		while (running) {

			try {
				currentTargetDepth++;

				ExecutorService es = Executors.newFixedThreadPool(2);

				Future[] futures = new Future[currentMoveList.length];

				for (int i = 0; i < currentMoveList.length; i++) {
					long[] board = mainBoard.clone();
					TiffanysBoard.makeMove(board, currentMoveList[i], color);

					futures[i] = es.submit(new TiffanysAlphaBetaRunnable(this, currentMoveList[i], currentTargetDepth - 1, -MAX_RANGE, MAX_RANGE, board, TiffanysBoard.invertColor(color), currentMoveList[i].hitmove, this.currentTargetDepth));
				}

				for (int i = 0; i < futures.length; i++) {
					futures[i].get();
				}

				es.shutdown();

			} catch (Exception ignored) {
				// ignored.printStackTrace();
			}

			TiffanysMove.sortMode = TiffanysMove.SORT_MODE_NORMAL;
			Arrays.sort(currentMoveList);
			this.processDepthFinished(createBestCurrentResult());

			// Abort search if winning mate is found
			boolean winningMoveFound = false;
			for (int i = 0; i < currentMoveList.length; i++) {
				if (currentMoveList[i].score >= MAX_MATE_RANGE) {
					winningMoveFound = true;
				}
			}

			if (winningMoveFound) {
				break;
			}

			// Abort search if only mate results
			boolean goOn = false;
			for (int i = 0; i < currentMoveList.length; i++) {
				if (currentMoveList[i].score > -MAX_MATE_RANGE) {
					goOn = true;
				}
			}

			if (!goOn) {
				break;
			}

			// Abort if fixed depth is reached
			if (engineSettings.GAME_MODE == EngineSettings.GAME_MODE_FIXED_DEPTH && engineSettings.FIXED_DEPTH == currentTargetDepth) {
				break;
			}

			abortable = true;
		}
		return createBestCurrentResult();
	}

	private EngineResult createBestCurrentResult() {
		TiffanysMove[] tiffMoves = currentMoveList.clone();
		TiffanysMove.sortMode = TiffanysMove.SORT_MODE_HIT_SCORE;
		Arrays.sort(tiffMoves);

		TiffanysMove selectedTiffMove = selectBestMove(tiffMoves);

		MoveInput selectedMove = null;
		if (selectedTiffMove != null) {
			selectedMove = selectedTiffMove.convertToMoveInput();
		}

		MoveInput[] moves = new MoveInput[tiffMoves.length];

		for (int i = 0; i < moves.length; i++) {
			moves[i] = tiffMoves[i].convertToMoveInput();
		}

		double ms = System.currentTimeMillis() - statSearchStarted;

		return new EngineResult(selectedMove, moves, ms, statEngineInfo.toString(), statNodes, statAbCutOffs, statPositionsEvaluated, currentTargetDepth);

	}

	@Override
	public String getEngineName() {
		return "Tiffanys Engine " + Charlies.VERSION;
	}

	private void initAi() {
		if (random == null) {
			random = new Random();
		}

		TranspositionTableTiff.reset();

		statNodes = 0;
		statPositionsEvaluated = 0;
		statAbCutOffs = 0;
		statEngineInfo = new StringBuffer();

		statSearchStarted = System.currentTimeMillis();

	}

	private TiffanysMove selectBestMove(TiffanysMove[] legalMoves) {

		int change = 0;
		int value = legalMoves[0].score;
		for (int i = 0; i < legalMoves.length; i++) {
			if (legalMoves[i].score != value) {
				change = i;
				break;
			}
		}

		if (change > 0) {
			change = random.nextInt(change);
		}

		return (legalMoves[change]);

	}





}